luci-app-attendedsysupgrade: sync with master branch
authorPaul Spooren <[email protected]>
Wed, 10 Feb 2021 03:45:52 +0000 (17:45 -1000)
committerPaul Spooren <[email protected]>
Thu, 18 Mar 2021 20:31:02 +0000 (10:31 -1000)
Use new LuCI JavaScript code instead of a mix of Lua and JS.

Signed-off-by: Paul Spooren <[email protected]>
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm [deleted file]
applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/40_luci-attendedsysupgrade [deleted file]
applications/luci-app-attendedsysupgrade/root/usr/share/luci/menu.d/luci-app-attendedsysupgrade.json
applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json [deleted file]
applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/root/www/luci-static/resources/attendedsysupgrade.js [deleted file]

diff --git a/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js
new file mode 100644 (file)
index 0000000..a99e646
--- /dev/null
@@ -0,0 +1,34 @@
+'use strict';
+'require view';
+'require form';
+
+return view.extend({
+       render: function() {
+               var m, s, o;
+
+               m = new form.Map('attendedsysupgrade', _('Attended Sysupgrade'),
+                       _('Attendedsysupgrade Configuration.')
+               );
+
+               s = m.section(form.TypedSection, 'server', _('Server'));
+               s.anonymous = true;
+
+               s.option(form.Value, 'url', _('Address'),
+                       _('Address of the sysupgrade server'));
+
+               s = m.section(form.TypedSection, 'client', _('Client'));
+               s.anonymous = true;
+
+               o = s.option(form.Flag, 'auto_search', _('Search on opening'),
+                       _('Search for new sysupgrades on opening the tab'));
+               o.default = '1';
+               o.rmempty = false;
+
+               o = s.option(form.Flag, 'advanced_mode', _('Advances Mode'),
+                       _('Show advanced options like packge list modification'));
+               o.default = '0';
+               o.rmempty = false;
+
+               return m.render();
+       }
+});
diff --git a/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js
new file mode 100644 (file)
index 0000000..a46bb3d
--- /dev/null
@@ -0,0 +1,366 @@
+'use strict';
+'require view';
+'require form';
+'require uci';
+'require rpc';
+'require ui';
+'require poll';
+'require request';
+'require dom';
+
+var callPackagelist = rpc.declare({
+       object: 'rpc-sys',
+       method: 'packagelist',
+});
+
+var callSystemBoard = rpc.declare({
+       object: 'system',
+       method: 'board'
+});
+
+var callUpgradeStart = rpc.declare({
+       object: 'rpc-sys',
+       method: 'upgrade_start',
+       params: ["keep"]
+});
+
+function install_sysupgrade(url, keep, sha256) {
+       displayStatus("notice spinning", E('p', _('Downloading firmware from server to browser')));
+       request.get(url, {
+                       headers: {
+                               'Content-Type': 'application/x-www-form-urlencoded'
+                       },
+                       responseType: 'blob'
+               })
+               .then(response => {
+                       var form_data = new FormData();
+                       form_data.append("sessionid", rpc.getSessionID());
+                       form_data.append("filename", "/tmp/firmware.bin");
+                       form_data.append("filemode", 600);
+                       form_data.append("filedata", response.blob());
+
+                       displayStatus("notice spinning", E('p', _('Uploading firmware from browser to device')));
+                       request.get(L.env.cgi_base + "/cgi-upload", {
+                                       method: 'PUT',
+                                       content: form_data
+                               })
+                               .then(response => response.json())
+                               .then(response => {
+                                       if (response.sha256sum != sha256) {
+                                               displayStatus("warning", [
+                                                       E('b', _('Wrong checksum')),
+                                                       E('p', _('Error during download of firmware. Please try again')),
+                                                       E('div', {
+                                                               'class': 'btn',
+                                                               'click': ui.hideModal
+                                                       }, _('Close'))
+                                               ]);
+                                       } else {
+                                               displayStatus('warning spinning', E('p', _('Installing the sysupgrade. Do not unpower device!')));
+                                                       L.resolveDefault(callUpgradeStart(keep), {}).then(response => {
+                                                               if (keep) {
+                                                                       ui.awaitReconnect(window.location.host);
+                                                               } else {
+                                                                       ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
+                                                               }
+                                                       });
+                                       }
+                               });
+               });
+}
+
+function request_sysupgrade(server_url, data) {
+       var res, req;
+
+       if (data.request_hash) {
+               req = request.get(server_url + "/api/build/" + data.request_hash)
+       } else {
+               req = request.post(server_url + "/api/build", {
+                       profile: data.board_name,
+                       version: data.version,
+                       packages: data.packages,
+                       diff_packages: true,
+               })
+       }
+
+       req.then(response => {
+               switch (response.status) {
+                       case 200:
+                               var res = response.json()
+                               var image;
+                               console.log(res)
+                               for (image of res.images) {
+                                       if (image.type == "sysupgrade") {
+                                               break;
+                                       }
+                               }
+                               if (image.name != undefined) {
+                                       var sysupgrade_url = server_url + "/store/" + res.bin_dir + "/" + image.name;
+
+                                       var keep = E('input', {
+                                               type: 'checkbox'
+                                       })
+                                       keep.checked = true;
+
+                                       var fields = [
+                                               _('Version'), res.version_number + ' ' + res.version_code,
+                                               _('File'), E('a', {
+                                                       'href': sysupgrade_url
+                                               }, image.name),
+                                               _('SHA256'), image.sha256,
+                                               _('Build Date'), res.build_at,
+                                               _('Target'), res.target,
+                                       ];
+
+                                       var table = E('div', {
+                                               'class': 'table'
+                                       });
+
+                                       for (var i = 0; i < fields.length; i += 2) {
+                                               table.appendChild(E('div', {
+                                                       'class': 'tr'
+                                               }, [
+                                                       E('div', {
+                                                               'class': 'td left',
+                                                               'width': '33%'
+                                                       }, [fields[i]]),
+                                                       E('div', {
+                                                               'class': 'td left'
+                                                       }, [(fields[i + 1] != null) ? fields[i + 1] : '?'])
+                                               ]));
+                                       }
+
+                                       var modal_body = [
+                                               table,
+                                               E('p', {}, E('label', {
+                                                       'class': 'btn'
+                                               }, [
+                                                       keep, ' ', _('Keep settings and retain the current configuration')
+                                               ])),
+                                               E('div', {
+                                                       'class': 'right'
+                                               }, [
+                                                       E('div', {
+                                                               'class': 'btn',
+                                                               'click': ui.hideModal
+                                                       }, _('Cancel')),
+                                                       ' ',
+                                                       E('div', {
+                                                               'class': 'btn cbi-button-action',
+                                                               'click': function() {
+                                                                       install_sysupgrade(sysupgrade_url, keep.checked, image.sha256)
+                                                               }
+                                                       }, _('Install Sysupgrade'))
+                                               ])
+                                       ]
+
+                                       ui.showModal(_('Successfully created sysupgrade image'), modal_body);
+                               }
+
+                               break;
+                       case 202:
+                               res = response.json()
+                               data.request_hash = res.request_hash;
+                               switch (res.status) {
+                                       case "queued":
+                                               displayStatus("notice spinning", E('p', _('Request in build queue')));
+                                               break;
+                                       case "started":
+                                               displayStatus("notice spinning", E('p', _('Building the sysupgrade image')));
+                                               break;
+                               }
+                               setTimeout(function() {
+                                       request_sysupgrade(server_url, data);
+                               }, 5000);
+                               break;
+                       case 400: // bad request
+                       case 422: // bad package
+                       case 500: // build failed
+                               res = response.json()
+                               var body = [
+                                       E('p', {}, _(res.message)),
+                                       E('p', {}, _("Please report the error message and request")),
+                                       E('b', {}, _("Request to server:")),
+                                       E('pre', {}, JSON.stringify(data, null, 4)),
+
+                               ]
+
+                               if (res.stdout) {
+                                       body.push(E('b', {}, "STDOUT:"))
+                                       body.push(E('pre', {}, res.stdout))
+
+                               }
+
+                               if (res.stderr) {
+                                       body.push(E('b', {}, "STDERR:"))
+                                       body.push(E('pre', {}, res.stderr))
+
+                               }
+
+                               body = body.concat([
+                                       E('div', {
+                                               'class': 'right'
+                                       }, [
+                                               E('div', {
+                                                       'class': 'btn',
+                                                       'click': ui.hideModal
+                                               }, _('Close'))
+                                       ])
+                               ]);
+                               ui.showModal(_('Error building the sysupgrade'), body);
+                               break;
+               }
+       });
+}
+
+function check_sysupgrade(server_url, current_version, board_name, packages) {
+       displayStatus("notice spinning", E('p', _('Searching for an available sysupgrade')));
+       var current_branch = current_version.split(".").slice(0, 2).join(".");
+       var candidates = [];
+       fetch(server_url + "/api/latest")
+               .then(response => response.json())
+               .then(response => {
+                       if (current_version == "SNAPSHOT") {
+                               candidates.push("SNAPSHOT");
+                       } else {
+                               for (let version of response["latest"]) {
+                                       var branch = version.split(".").slice(0, 2).join(".");
+
+                                       // already latest version installed
+                                       if (current_version == version) {
+                                               break;
+                                       }
+
+                                       // warn user that a new major release would be installed
+                                       //if (current_branch != branch) {
+                                       //      branch["warn_branch_jump"] = true;
+                                       //}
+
+                                       candidates.unshift(version);
+
+                                       // don't offer branches older than the current
+                                       if (current_branch == branch) {
+                                               break;
+                                       }
+                               }
+                       }
+                       if (candidates) {
+                               var m, s, o;
+                               var advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
+
+                               console.log(candidates);
+
+                               var mapdata = {
+                                       request: {
+                                               board_name: board_name,
+                                               version: candidates[0],
+                                               packages: Object.keys(packages).sort(),
+                                       }
+                               }
+
+                               m = new form.JSONMap(mapdata, '');
+
+                               s = m.section(form.NamedSection, 'request', 'example', '',
+                                       'Use defaults for the safest update');
+                               o = s.option(form.ListValue, 'version', 'Select firmware version');
+                               for (let candidate of candidates) {
+                                       o.value(candidate, candidate);
+                               }
+
+                               if (advanced_mode == 1) {
+                                       o = s.option(form.Value, 'board_name', 'Board Name / Profile');
+                                       o = s.option(form.DynamicList, 'packages', 'Packages');
+                               }
+
+
+                               m.render()
+                                       .then(function(form_rendered) {
+                                               ui.showModal(_('New upgrade available'), [
+                                                       form_rendered,
+                                                       E('div', {
+                                                               'class': 'right'
+                                                       }, [
+                                                               E('div', {
+                                                                       'class': 'btn',
+                                                                       'click': ui.hideModal
+                                                               }, _('Cancel')),
+                                                               ' ',
+                                                               E('div', {
+                                                                       'class': 'btn cbi-button-action',
+                                                                       'click': function() {
+                                                                               m.save().then(foo => {
+                                                                                       request_sysupgrade(
+                                                                                               server_url, mapdata.request
+                                                                                       )
+                                                                               });
+                                                                       }
+                                                               }, _('Request Sysupgrade'))
+                                                       ])
+                                               ]);
+                                       });
+                       } else {
+                               ui.showModal(_('No upgrade available'), [
+                                       E('p', {}, _("The device runs the latest firmware version")),
+                                       E('div', {
+                                               'class': 'right'
+                                       }, [
+                                               E('div', {
+                                                       'class': 'btn',
+                                                       'click': ui.hideModal
+                                               }, _('Close'))
+                                       ])
+                               ]);
+                       }
+               });
+}
+
+function displayStatus(type, content) {
+       if (type) {
+               var message = ui.showModal('', '');
+
+               message.classList.add('alert-message');
+               DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
+
+               if (content)
+                       dom.content(message, content);
+       } else {
+               ui.hideModal();
+       }
+}
+
+return view.extend({
+       load: function() {
+               return Promise.all([
+                       L.resolveDefault(callPackagelist(), {}),
+                       L.resolveDefault(callSystemBoard(), {}),
+                       uci.load('attendedsysupgrade')
+               ]);
+       },
+       render: function(res) {
+               var packages = res[0].packages;
+               var current_version = res[1].release.version;
+               var board_name = res[1].board_name;
+               var auto_search = uci.get_first('attendedsysupgrade', 'client', 'auto_search') || 1;
+               var server_url = uci.get_first('attendedsysupgrade', 'server', 'url');
+
+               var view = [
+                       E('h2', _("Attended Sysupgrade")),
+                       E('p', _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
+                       E('p', _('This is done by building a new firmware on demand via an online service.'))
+               ];
+
+               if (auto_search == 1) {
+                       check_sysupgrade(server_url, current_version, board_name, packages)
+               }
+
+               view.push(E('p', {
+                       'class': 'btn cbi-button-positive',
+                       'click': function() {
+                               check_sysupgrade(server_url, current_version, board_name, packages)
+                       }
+               }, _('Search for sysupgrade')));
+
+               return view;
+       },
+
+});
diff --git a/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm b/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm
deleted file mode 100644 (file)
index c925955..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<%
--- all lua code provided by https://github.com/jow-/
--- thank you very much!
-
-    function apply_acls(filename, session)
-        local json = require "luci.jsonc"
-        local util = require "luci.util"
-        local fs   = require "nixio.fs"
-
-        local grants = { }
-
-        local acl = json.parse(fs.readfile(filename))
-        if type(acl) ~= "table" then
-            return
-        end
-
-        local group, perms
-        for group, perms in pairs(acl) do
-            local perm, scopes
-            for perm, scopes in pairs(perms) do
-                if type(scopes) == "table" then
-                    local scope, objects
-                    for scope, objects in pairs(scopes) do
-                        if type(objects) == "table" then
-                            if not grants[scope] then
-                                grants[scope] = { }
-                            end
-
-                            if next(objects) == 1 then
-                                local _, object
-                                for _, object in ipairs(objects) do
-                                    if not grants[scope][object] then
-                                        grants[scope][object] = { }
-                                    end
-                                    table.insert(grants[scope][object], perm)
-                                end
-                            else
-                                local object, funcs
-                                for object, funcs in pairs(objects) do
-                                    if type(funcs) == "table" then
-                                        local _, func
-                                        for _, func in ipairs(funcs) do
-                                            if not grants[scope][object] then
-                                                grants[scope][object] = { }
-                                            end
-                                            table.insert(grants[scope][object], func)
-                                        end
-                                    end
-                                end
-                            end
-                        end
-                    end
-                end
-            end
-        end
-
-        local _, scope, object, func
-        for scope, _ in pairs(grants) do
-            local objects = { }
-            for object, _ in pairs(_) do
-                for _, func in ipairs(_) do
-                    table.insert(objects, { object, func })
-                end
-            end
-
-            util.ubus("session", "grant", {
-                ubus_rpc_session = session,
-                scope = scope, objects = objects
-            })
-        end
-    end
-
-    apply_acls("/usr/share/rpcd/acl.d/attendedsysupgrade.json", luci.dispatcher.context.authsession)
-%>
-<%+header%>
-<h2 name="content"><%:Attended Sysupgrade%></h2>
-<div class="cbi-map-descr">
-       Easily search and install new releases and package upgrades. Sysupgrade firmware are created on demand based on locally installed packages.
-</div>
-<div style="display: none" id="status_box" class="alert-message info"></div>
-<div style="display: none" id="packages" class="alert-message success"></div>
-<p>
-<textarea style="display: none; width: 100%;" id="edit_packages" rows="15"></textarea>
-</p>
-<fieldset class="cbi-section">
-       <form method="post" action="">
-               <div class="cbi-selection-node">
-                       <div class="cbi-value" id="keep_container" style="display: none">
-                               <div class="cbi-section-descr">
-                                       Check "Keep settings" to retain the current configuration (requires a compatible firmware).
-                               </div>
-                               <label class="cbi-value-title" for="keep">Keep settings:</label>
-                               <div class="cbi-value-field">
-                                       <input name="keep" id="keep" checked="checked" type="checkbox">
-                               </div>
-                       </div>
-                       <div class="cbi-value" id="edit_button" style="display: none">
-                               <div class="cbi-value-field">
-                                       <input class="cbi-button" value="Edit installed packages" onclick="edit_packages()" type="button">
-                               </div>
-                       </div>
-                       <div class="cbi-value cbi-value" id="server_div" style="display:none">
-                               <label class="cbi-value-title" for="server">Server:</label>
-                               <div class="cbi-value-field">
-                                       <input onclick="edit_server()" class="cbi-button cbi-button-edit" value="" type="button" id="server" name="server">
-                               </div>
-                       </div>
-                       <div class="cbi-value cbi-value-last">
-                               <div class="cbi-value-field">
-                                       <input class="cbi-button cbi-button-apply" value="Search for upgrades" style="display: none" onclick="upgrade_check()" type="button" id="upgrade_button">
-                               </div>
-                       </div>
-               </div>
-       </form>
-</fieldset>
-<script type="text/javascript">
-       data = {};
-       data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
-       origin = document.location.href.replace(location.pathname, "")
-       ubus_url = origin + "/ubus/"
-</script>
-<script type="text/javascript" src="<%=resource%>/attendedsysupgrade.js"></script>
-<%+footer%>
diff --git a/applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/40_luci-attendedsysupgrade b/applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/40_luci-attendedsysupgrade
deleted file mode 100755 (executable)
index 48ae4cc..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-/etc/init.d/uhttpd restart
-/etc/init.d/rpcd reload
-
-return 0
index 79d82a828f59fc974a524a305bac1ec4be0a8bdc..33d7019fa31cc13cf9d5bfdcca13ea852614d9e2 100644 (file)
@@ -1,13 +1,31 @@
 {
-       "admin/system/attended_sysupgrade": {
+       "admin/system/attendedsysupgrade": {
                "title": "Attended Sysupgrade",
-               "order": 1,
+               "order": 60,
                "action": {
-                       "type": "template",
-                       "path": "attendedsysupgrade"
+                       "type": "firstchild"
                },
                "depends": {
-                       "acl": [ "attendedsysupgrade" ]
+                       "acl": [ "luci-app-attendedsysupgrade" ],
+                       "uci": { "attendedsysupgrade": true }
+               }
+       },
+
+       "admin/system/attendedsysupgrade/overview": {
+               "title": "Overview",
+               "order": 1,
+               "action": {
+                       "type": "view",
+                       "path": "attendedsysupgrade/overview"
+               }
+       },
+
+       "admin/system/attendedsysupgrade/configuration": {
+               "title": "Configuration",
+               "order": 2,
+               "action": {
+                       "type": "view",
+                       "path": "attendedsysupgrade/configuration"
                }
        }
 }
diff --git a/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json b/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json
deleted file mode 100644 (file)
index 7549319..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-       "attendedsysupgrade": {
-               "description": "attended sysupgrade via rpcd and luci",
-               "read": {
-                       "ubus": {
-                               "rpc-sys": [
-                                       "upgrade_start",
-                                       "packagelist"
-                               ],
-                               "system": [
-                                       "board",
-                                       "info"
-                               ],
-                               "uci": [
-                                       "get", "set", "commit"
-                               ]
-                       },
-                       "uci": [
-                               "attendedsysupgrade"
-                       ]
-               },
-               "write": {
-                       "cgi-io": [
-                               "upload"
-                       ],
-                       "uci": [
-                               "attendedsysupgrade"
-                       ]
-               }
-       }
-}
diff --git a/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json b/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json
new file mode 100644 (file)
index 0000000..e3ceeaa
--- /dev/null
@@ -0,0 +1,17 @@
+{
+       "luci-app-attendedsysupgrade": {
+               "description": "Grant UCI access to LuCI app attendedsysupgrade",
+               "read": {
+                       "uci": ["attendedsysupgrade"],
+                       "ubus": {
+                               "rpc-sys": ["upgrade_start", "packagelist"]
+                       }
+               },
+               "write": {
+                       "uci": ["attendedsysupgrade"],
+                       "ubus": {
+                               "rpc-sys": ["upgrade_start"]
+                       }
+               }
+       }
+}
diff --git a/applications/luci-app-attendedsysupgrade/root/www/luci-static/resources/attendedsysupgrade.js b/applications/luci-app-attendedsysupgrade/root/www/luci-static/resources/attendedsysupgrade.js
deleted file mode 100644 (file)
index b75e90d..0000000
+++ /dev/null
@@ -1,384 +0,0 @@
-function $(s) {
-    return document.getElementById(s.substring(1));
-}
-
-function show(s) {
-    $(s).style.display = 'block';
-}
-
-function hide(s) {
-    $(s).style.display = 'none';
-}
-
-function set_server() {
-    hide("#status_box");
-    data.url = $("#server").value;
-    ubus_call("uci", "set", {
-        "config": "attendedsysupgrade",
-        "section": "server",
-        values: {
-            "url": data.url
-        }
-    })
-    ubus_call("uci", "commit", {
-        "config": "attendedsysupgrade"
-    })
-    var server_button = $("#server")
-    server_button.type = 'button';
-    server_button.className = 'cbi-button cbi-button-edit';
-    server_button.parentElement.removeChild($("#button_set"));
-    server_button.onclick = edit_server;
-}
-
-function edit_server() {
-    $("#server").type = 'text';
-    $("#server").onkeydown = function(event) {
-        if (event.key === 'Enter') {
-            set_server();
-            return false;
-        }
-    }
-    $("#server").className = '';
-    $("#server").onclick = null;
-
-    var button_set = document.createElement("input");
-    button_set.type = "button";
-    button_set.value = "Save";
-    button_set.name = "button_set";
-    button_set.id = "button_set";
-    button_set.className = 'cbi-button cbi-button-save';
-    button_set.onclick = set_server
-    $("#server").parentElement.appendChild(button_set);
-}
-
-function edit_packages() {
-    data.edit_packages = true
-    hide("#edit_button");
-    $("#edit_packages").value = data.packages.join("\n");
-    show("#edit_packages");
-}
-
-// initial setup, get system information
-function setup() {
-    ubus_call("rpc-sys", "packagelist", {}, "packages");
-    ubus_call("system", "board", {}, "release");
-    ubus_call("system", "board", {}, "board_name");
-    ubus_call("system", "info", {}, "memory");
-    uci_get({
-        "config": "attendedsysupgrade",
-        "section": "server",
-        "option": "url"
-    })
-    uci_get({
-        "config": "attendedsysupgrade",
-        "section": "client",
-        "option": "upgrade_packages"
-    })
-    uci_get({
-        "config": "attendedsysupgrade",
-        "section": "client",
-        "option": "advanced_mode"
-    })
-    uci_get({
-        "config": "attendedsysupgrade",
-        "section": "client",
-        "option": "auto_search"
-    })
-    setup_ready();
-}
-
-function setup_ready() {
-    // checks if a async ubus calls have finished
-    if (ubus_counter != ubus_closed) {
-        setTimeout(setup_ready, 300)
-    } else {
-        if (data.auto_search == 1) {
-            upgrade_check();
-        } else {
-            show("#upgrade_button");
-            show("#server_div");
-            $("#server").value = data.url;
-        }
-    }
-}
-
-function uci_get(option) {
-    // simple wrapper to get a uci value store in data.<option>
-    ubus_call("uci", "get", option, option["option"])
-}
-
-ubus_counter = 0;
-ubus_closed = 0;
-
-function ubus_call(command, argument, params, variable) {
-    var request_data = {};
-    request_data.jsonrpc = "2.0";
-    request_data.id = ubus_counter;
-    request_data.method = "call";
-    request_data.params = [data.ubus_rpc_session, command, argument, params]
-    var request_json = JSON.stringify(request_data)
-    ubus_counter++;
-    var request = new XMLHttpRequest();
-    request.open("POST", ubus_url, true);
-    request.setRequestHeader("Content-type", "application/json");
-    request.onload = function(event) {
-        if (request.status === 200) {
-            var response = JSON.parse(request.responseText)
-            if (!("error" in response) && "result" in response) {
-                if (response.result.length === 2) {
-                    if (command === "uci") {
-                        data[variable] = response.result[1].value
-                    } else {
-                        data[variable] = response.result[1][variable]
-                    }
-                }
-            } else {
-                set_status("danger", "<b>Ubus call failed:</b><br />Request: " + request_json + "<br />Response: " + JSON.stringify(response))
-            }
-            ubus_closed++;
-        }
-    }
-    request.send(request_json);
-}
-
-function set_status(type, message, loading, show_log) {
-    $("#status_box").className = "alert-message " + type;
-    var loading_image = '';
-    if (loading) {
-        loading_image = '<img src="/luci-static/resources/icons/loading.gif" alt="Loading" style="vertical-align:middle"> ';
-    }
-    if (data.buildlog_url && show_log) {
-        message += ' <p><a target="_blank" href="' + data.buildlog_url + '">Build log</a></p>'
-    }
-    $("#status_box").innerHTML = loading_image + message;
-    show("#status_box")
-}
-
-function upgrade_check() {
-    var current_version = data.release.version.toLowerCase();
-    var current_branch = current_version.split('.').slice(0, 2).join('.')
-    var candidates = []
-    hide("#status_box");
-    hide("#server_div");
-    set_status("info", "Searching for upgrades", true);
-    fetch(data.url + "/api/versions")
-        .then(response => response.json())
-        .then(response => {
-            var branches = response["branches"]
-            for (i in branches) {
-                // handle snapshots in a special way - as always
-                if (current_version == "snapshot" && branches[i]["latest"] == "snapshot") {
-                    candidates.unshift(branches[i])
-                    break
-                }
-
-                if (current_version == branches[i]["latest"]) {
-                    break
-                }
-                if (current_branch != branches[i]["name"]) {
-                    branches[i]["warn_branch_jump"] = true
-                }
-                candidates.unshift(branches[i])
-                if (current_branch == branches[i]["name"]) {
-                    // don't offer branches older than the current
-                    break
-                }
-            }
-
-            if (candidates.length > 0) {
-                var info_output = "<h3>New release <b>" + candidates[0].latest + "</b> available</h3>"
-                info_output += "Installed version: " + data.release.version
-
-                // tell server the currently installed version
-                request_dict.current_version = request_dict.version;
-                // tell server what version to install
-                request_dict.version = candidates[0].latest;
-                // tell server to diff the requested packages with the default packages
-                // this allows to not automatically re-install default packages which
-                // where dropped in later releases
-                request_dict.diff_packages = true;
-
-                set_status("success", info_output)
-
-                if (data.advanced_mode == 1) {
-                    show("#edit_button");
-                }
-                var upgrade_button = $("#upgrade_button")
-                upgrade_button.value = "Request firmware";
-                upgrade_button.style.display = "block";
-                upgrade_button.disabled = false;
-                upgrade_button.onclick = upgrade_request;
-
-            } else {
-                set_status("success", "No upgrades available")
-
-            }
-        });
-
-}
-
-function upgrade_request() {
-    // Request firmware using the following parameters
-    // distro, version, target, board_name, packages
-    $("#upgrade_button").disabled = true;
-    hide("#edit_packages");
-    hide("#edit_button");
-    hide("#keep_container");
-
-    // add board info to let server determine profile
-    request_dict.target = data.release.target
-    request_dict.profile = data.board_name
-
-    if (data.edit_packages == true) {
-        request_dict.packages = $("#edit_packages").value.split("\n")
-    } else {
-        request_dict.packages = Object.keys(data.packages);
-    }
-    server_request()
-}
-
-function upgrade_request_callback(response) {
-    var sysupgrade_file = "";
-    console.log(response)
-    for (i in response.images) {
-        if (response.images[i].type == "sysupgrade") {
-            sysupgrade_file = response.images[i].name;
-        }
-    }
-    if (sysupgrade_file != "") {
-        data.sysupgrade_url = data.url + '/store/' + response.bin_dir + '/' + sysupgrade_file
-        var info_output = '<h3>Firmware created</h3><p>Created file: <a href="' + data.sysupgrade_url + '">' + sysupgrade_file + '</p></a>'
-        set_status("success", info_output, false, true);
-
-        show("#keep_container");
-        var upgrade_button = $("#upgrade_button")
-        upgrade_button.disabled = false;
-        upgrade_button.style.display = "block";
-        upgrade_button.value = "Flash firmware";
-        upgrade_button.onclick = download_image;
-    } else {
-        set_status("danger", "Firmware build successfull but device not sysupgrade compatible!")
-    }
-}
-
-function flash_image() {
-    // Flash image via rpc-sys upgrade_start
-    set_status("warning", "Flashing firmware. Don't unpower device", true)
-    ubus_call("rpc-sys", "upgrade_start", {
-        "keep": $("#keep").checked
-    }, 'message');
-    ping_max = 3600; // in seconds
-    setTimeout(ping_ubus, 10000)
-}
-
-function ping_ubus() {
-    // Tries to connect to ubus. If the connection fails the device is likely still rebooting.
-    // If more time than ping_max passes update may failed
-    if (ping_max > 0) {
-        ping_max--;
-        var request = new XMLHttpRequest();
-        request.open("GET", ubus_url, true);
-        request.addEventListener('error', function(event) {
-            set_status("warning", "Rebooting device - please wait!", true);
-            setTimeout(ping_ubus, 5000)
-        });
-        request.addEventListener('load', function(event) {
-            set_status("success", "Success! Please reload web interface");
-            $("#upgrade_button").value = "Reload page";
-            show("#upgrade_button");
-            $("#upgrade_button").disabled = false;
-            $("#upgrade_button").onclick = function() {
-                location.reload();
-            }
-        });
-        request.send();
-    } else {
-        set_status("danger", "Web interface could not reconnect to your device. Please reload web interface or check device manually")
-    }
-}
-
-function upload_image(blob) {
-    // Uploads received blob data to the server using cgi-io
-    set_status("info", "Uploading firmware to device", true);
-    var request = new XMLHttpRequest();
-    var form_data = new FormData();
-
-    form_data.append("sessionid", data.ubus_rpc_session)
-    form_data.append("filename", "/tmp/firmware.bin")
-    form_data.append("filemode", 755) // insecure?
-    form_data.append("filedata", blob)
-
-    request.addEventListener('load', function(event) {
-        request_json = JSON.parse(request.responseText)
-        flash_image();
-    });
-
-    request.addEventListener('error', function(event) {
-        set_status("danger", "Upload of firmware failed, please retry by reloading web interface")
-    });
-
-    request.open('POST', origin + '/cgi-bin/cgi-upload');
-    request.send(form_data);
-}
-
-
-function download_image() {
-    // Download image from server once the url was received by upgrade_request
-    hide("#keep_container");
-    hide("#upgrade_button");
-    var download_request = new XMLHttpRequest();
-    download_request.open("GET", data.sysupgrade_url);
-    download_request.responseType = "arraybuffer";
-
-    download_request.onload = function() {
-        if (this.status === 200) {
-            var blob = new Blob([download_request.response], {
-                type: "application/octet-stream"
-            });
-            upload_image(blob)
-        }
-    };
-    set_status("info", "Downloading firmware to web browser memory", true);
-    download_request.send();
-}
-
-function server_request() {
-    fetch(data.url + "/api/build", {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            },
-            body: JSON.stringify(request_dict)
-        })
-        .then(response => {
-            switch (response.status) {
-                case 200:
-                    response.json()
-                        .then(response => {
-                            upgrade_request_callback(response)
-                        });
-                    break;
-                case 202:
-                    set_status("info", "Processing request", true);
-                    setTimeout(function() {
-                        server_request()
-                    }, 5000)
-                    break;
-                case 400: // bad request
-                case 422: // bad package
-                case 500: // build failed
-                    console.log('error (' + response.status + ')');
-                    response.json()
-                        .then(response => {
-                            if (response.buildlog) {
-                                data.buildlog_url = data.url + '/' + response.bin_dir + '/buildlog.txt';
-                            }
-                            set_status("danger", response.message);
-                        });
-                    break;
-            }
-        });
-}
-
-request_dict = {}
-document.onload = setup()